<?php

namespace app\shop\library\pay;

use app\shop\enums\payment\PayWay;
use app\shop\library\pay\implements\AlipayImplement;
use app\shop\library\pay\implements\BalanceImplement;
use app\shop\library\pay\implements\WxpayImplement;
use app\shop\library\ShopHelper;
use Exception;
use think\helper\Arr;
use Throwable;
use Yansongda\Artful\Exception\ContainerException;
use Yansongda\Artful\Exception\InvalidParamsException;
use Yansongda\Artful\Exception\ServiceNotFoundException;

class PayProvider
{
    protected object $provider;

    protected string $payWay;

    /**
     * @throws Exception
     */
    public function __construct(string $payWay)
    {
        $this->provider = match ($payWay) {
            PayWay::WXPAY => new WxpayImplement(),
            PayWay::BALANCE => new BalanceImplement(),
            PayWay::ALIPAY => new AlipayImplement(),
            default => throw new Exception('支付方式暂不支持'),
        };

        $this->payWay = $payWay;
    }

    public function getProvider(): WxpayImplement|BalanceImplement|AlipayImplement
    {
        return $this->provider;
    }

    /**
     * 支付
     * @param $params
     * <br>(必须) out_trade_no 第三方交易单号
     * <br>(必须) source 订单来源/平台
     * <br>(必须) amount 付款金额
     * <br>(必须) desc 商品描述
     * <br>(必须) user_id 支付用户ID
     * <br> notify_url 回调通知url
     * <br> log_file_path 日志文件保存路径
     * @return array
     * @throws Exception
     */
    public function pay($params): array
    {
        $this->checkParams($params, [
            'out_trade_no', 'source', 'amount', 'desc', 'user_id'
        ]);

        if (ShopHelper::is_third_party_payment($this->payWay)) {
            if (Arr::get($params, 'notify_url')) {
                $this->provider->setNotifyUrl($params['notify_url']);
            } else {
                $this->provider->setNotifyUrl(ShopHelper::full_api_url("/notify/$this->payWay"));
            }

            if (Arr::get($params, 'return_url')) {
                $this->provider->setReturnUrl($params['return_url']);
            }

            if (Arr::get($params, 'log_file_path')) {
                $this->provider->setLogFilePath($params['log_file_path']);
            } else {
                $this->provider->setLogFilePath(root_path() . "runtime/log/$this->payWay/pay.log");
            }

            if ($this->payWay === PayWay::ALIPAY) {
                if ($params['source'] == 'h5') {
                    return $this->provider->h5($params);
                }
            }

            if ($this->payWay === PayWay::WXPAY) {
                if ($params['source'] == 'miniapp') {
                    return $this->provider->miniapp($params);
                }
            }
        }

        return $this->provider->pay($params);
    }

    /**
     * 退款
     * @param array $params
     * <br>(必须) out_trade_no 第三方交易单号
     * <br>(必须) source 订单来源/平台
     * <br>(必须) refund_amount 退款金额
     * <br>(必须) total_amount 订单实付总金额
     * <br>notify_url 回调通知url
     * <br>log_file_path 日志文件保存路径
     * @return array
     * @throws Exception
     */
    public function refund(array $params): array
    {
        $this->checkParams($params, [
            'out_trade_no', 'source', 'refund_amount', 'total_amount'
        ]);

        if ($params['refund_amount'] > $params['total_amount']) {
            throw new Exception('退款金额不能大于实付总金额');
        }

        if (ShopHelper::is_third_party_payment($this->payWay)) {
            if (Arr::get($params, 'notify_url')) {
                $this->provider->setNotifyUrl($params['notify_url']);
            } else {
                $this->provider->setNotifyUrl(ShopHelper::full_api_url("/notify/{$this->payWay}Refund"));
            }

            if (Arr::get($params, 'log_file_path')) {
                $this->provider->setLogFilePath($params['log_file_path']);
            } else {
                $this->provider->setLogFilePath(root_path() . "runtime/log/$this->payWay/refund.log");
            }

            if ($this->payWay == PayWay::ALIPAY) {
                $params['_action']     = $this->getAlipayAction($params['source']);
                $params['_notify_url'] = ShopHelper::full_api_url("/notify/{$this->payWay}Refund");
            }
            if ($this->payWay == PayWay::WXPAY) {
                $params['_action'] = $this->getWxpayAction($params['source']);
            }
        }

        try {
            return $this->provider->refund($params);
        } catch (Throwable $e) {
            throw new Exception('退款异常: ' . $e->getMessage());
        }
    }

    /**
     * 查询订单
     * @param array $params
     * <br>(必须) out_trade_no 第三方交易单号
     * <br>(必须) source
     * @return array
     * @throws ContainerException
     * @throws InvalidParamsException
     * @throws ServiceNotFoundException
     * @throws Exception
     */
    public function query(array $params): array
    {
        $this->checkParams($params, [
            'out_trade_no', 'source'
        ]);

        $outTradeNo = $params['out_trade_no'];
        $source     = $params['source'];

        $queryParams = [
            'out_trade_no' => $outTradeNo,
        ];

        if (ShopHelper::is_third_party_payment($this->payWay)) {
            if ($this->payWay == PayWay::ALIPAY) {
                $queryParams['_action'] = $this->getAlipayAction($source);
            }
            if ($this->payWay == PayWay::WXPAY) {
                $queryParams['_action'] = $this->getWxpayAction($source);
            }
        }

        return $this->provider->query($queryParams);
    }

    /**
     * 查询退款
     * @param array $params
     * <br>(必须) out_trade_no 第三方交易流水号(微信支付传out_refund_no)
     * <br>(必须) source 订单来源
     * @return array
     * @throws Exception
     */
    public function queryRefund(array $params): array
    {
        $this->checkParams($params, [
            'out_trade_no', 'source'
        ]);

        if (ShopHelper::is_third_party_payment($this->payWay)) {
            if ($this->payWay == PayWay::ALIPAY) {
                $params['_action'] = $this->getAlipayAction($params['source'], true);
            }
            if ($this->payWay == PayWay::WXPAY) {
                $params['_action'] = $this->getWxpayAction($params['source'], true);
            }
        }

        try {
            return $this->provider->queryRefund($params);
        } catch (Throwable $e) {
            throw new Exception('退款订单查询异常: ' . $e->getMessage());
        }
    }

    /**
     * 获取支付宝查询方法
     * @param string $source
     * @param bool   $isRefund
     * @return string
     */
    private function getAlipayAction(string $source, bool $isRefund = false): string
    {
        $action = match ($source) {
            'h5' => 'h5',
            default => 'web',
        };

        if ($isRefund) {
            $action = 'refund_' . $action;
        }

        return $action;
    }

    /**
     * 获取微信查询方法
     * @param string $source
     * @param bool   $isRefund
     * @return string
     */
    private function getWxpayAction(string $source, bool $isRefund = false): string
    {
        $action = match ($source) {
            'miniapp' => 'mini',
            default => 'jsapi',
        };

        if ($isRefund) {
            $action = 'refund_' . $action;
        }

        return $action;
    }

    /**
     * 校验参数
     * @param array $params        传递参数
     * @param array $requireParams 必传参数列表
     * @return void
     * @throws Exception
     */
    private function checkParams(array $params, array $requireParams): void
    {
        array_filter($requireParams, function ($param) use ($params) {
            if (!Arr::get($params, $param)) {
                throw new Exception("参数{$param}不能为空");
            }
        });
    }
}
